package com.xmlt.core.template.instance;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.xpath.XPathExpressionException;

import org.eclipse.core.runtime.IAdaptable;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import com.xmlt.core.template.TemplateException;
import com.xmlt.core.template.TemplateManager;
import com.xmlt.core.template.TemplateParameter;
import com.xmlt.core.template.Utils;
import com.xmlt.core.template.contentgenerators.IContentGenerator;

public class Instance extends AbstractInstance implements IAdaptable {

	private Document document;
	private List<String> childrenNames;
	private Map<String, AbstractInstance> childInstances;
	private String instanceReference;

	public Instance(Element el, TemplateManager tm, TemplateParameter tp,
			AbstractInstance parent, String reference) throws TemplateException {
		super(el, tm, tp, parent);
		String ref = el.getAttribute("templateRef");
		if (tp != null) {
			ref = tp.getReference();
		}
		setTemplate(tm.findTemplate(ref));
		childInstances = new HashMap<String, AbstractInstance>();
		childrenNames = new ArrayList<String>();
		instanceReference = reference;
		loadChildInstances();
	}

	public Instance(Element el, Document d, TemplateManager tm,
			TemplateParameter tp, String reference) throws TemplateException {
		this(el, tm, tp, null, reference);
		document = d;
	}

	public Document getDocument() {
		return document;
	}

	@Override
	public synchronized AbstractInstance lookup(String path) {
		if (path.indexOf(".") > -1) {
			String param = path.substring(0, path.indexOf("."));
			AbstractInstance p = _lookup(param);
			if (p == null)
				return null;

			return p.lookup(path.substring(path.indexOf(".") + 1));
		}
		return _lookup(path);
	}

	private AbstractInstance _lookup(String path) {
		if (path.indexOf("[") > -1) {
			String paramName = path.substring(0, path.indexOf("["));
			String num = path.substring(path.indexOf("[") + 1,
					path.indexOf("]"));
			AbstractInstance p = _lookup(paramName);
			if (!(p instanceof MultiInstance))
				return null;

			int position = Integer.parseInt(num);
			return ((MultiInstance) p).getItem(position);
		}
		return childInstances.get(path);
	}

	public List<String> getChildrenNames() {
		return childrenNames;
	}

	public Map<String, AbstractInstance> getChildren() {
		return childInstances;
	}

	private void loadChildInstances() throws TemplateException {
		List<TemplateParameter> templateParams = getTemplate().getParameters();
		for (TemplateParameter templateParam : templateParams) {
			String nameXpath = "parameter[@name='" + templateParam.getName()
					+ "']";
			try {
				NodeList paramNodeList = Utils.evaluateXPath(getElement(),
						nameXpath);
				if (paramNodeList.getLength() < 1) {
					templateParam.generateParameterInInstanceNode(getElement());
				}
			} catch (XPathExpressionException e) {
				throw new TemplateException(e);
			}
		}

		NodeList params = null;
		try {
			params = Utils.evaluateXPath(getElement(), "parameter");
		} catch (XPathExpressionException e) {
			throw new TemplateException(e);
		}
		if (params == null)
			return;

		for (int i = 0; i < params.getLength(); i++) {
			Element pEl = (Element) params.item(i);
			TemplateParameter tp = getTemplate().lookupParameter(
					pEl.getAttribute("name"));
			if (tp == null) {
				pEl.getParentNode().removeChild(pEl);
				continue;
			}

			String paramName = pEl.getAttribute("name");
			try {
				AbstractInstance inst = InstanceFactory.newInstance(pEl,
						getTemplate(), getManager(), tp, this);
				childInstances.put(paramName, inst);
				childrenNames.add(paramName);
			} catch (TemplateException te) {
				String parentPath = getPath() == null ? "<root node>"
						: getPath();
				throw new TemplateException("Could not load parameter "
						+ parentPath + "." + paramName + ":" + te.getMessage(),
						te);
			}
		}
	}

	@Override
	public Object getAdapter(Class cls) {
		if (cls.equals(this.getClass())) {
			return this;
		}

		IContentGenerator gen = getTemplate().getGenerator();
		if (gen != null && gen.getTargetClass().equals(cls)) {
			return gen.getContent(this);
		}

		return null;
	}
	
	public void setReference(String ref) {
		this.instanceReference = ref;
	}

	public String getReference() {
		if (instanceReference == null) {
			AbstractInstance parent = this;
			while (parent.getParent() != null)
				parent = parent.getParent();
			
			return ((Instance) parent).getReference() + "#" + getPath();
		}
		return instanceReference;
	}

	public void setParameter(TemplateParameter parameterReference) {
		parameter = parameterReference;
	}

}
